Redis 高可用-1
主从复制
避免单点故障的最好方式就是把数据备份到其他服务器上,形成集群对外提供服务
Redis 提供了主从复制模式,可以保证多台服务器之间的数据一致性
主从服务器之间采用读写分离的方式,主服务器可以进行读写操作,当发生写操作时将写操作同步给服务器,而从服务器为只读模式,接受主服务器同步的命令并执行,所有数据修改只在主服务器上进行,使得主从服务器数据一致
第一次同步
在 Redis 上使用 replicaof(Redis 5.0 之前使用 slaveof)命令形成主从服务器关系
# 在服务器 B 执行这条命令,使 B 变成 A 的从服务器
replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>
第一次同步的三个阶段
- 建立连接、协商同步
执行了 replicaof 命令后从服务器就会给主服务器发送 psync 命令,表示要进行数据同步
主服务器收到 psync 命令后会用 FULLRESYNC(表示全量复制) 作为响应命令返回,并带上主服务器的 runID 和 主服务器目前的复制进度 offset,从服务器收到响应后会记录这两个值
第一阶段的工作是为了全量复制做准备
psync 包含两个参数- runID:每个 Redis 服务器启动时都会自动生产一个随机的 ID 作为自己的唯一标识,第一次主从同步时设置为 ? (因为主服务器 runID 未知)
- offset:表示复制进度,第一次同步时为 -1
- 主服务器发送同步数据给从服务器
主服务器会执行 bgsave 命令生成 RDB 文件,然后将文件发送给从服务器,从服务器收到 RDB 文件后会先清空当前数据,然后载入 RDB 文件
由于 bgsave 不会阻塞主线程,生成 RDB 过程中 Redis 依然可以正常处理命令,期间的写操作命令没有记录到刚生成的 RDB 文件中,会产生主从服务器数据不一致的问题
为了保证数据一致性,主服务器会在三个时间间隙中将收到的写操作命令写入到 replication buffer 缓冲区中- 主服务器生成 RDB 文件期间
- 主服务器发送 RDB 文件给从服务器期间
- 从服务器加载 RDB 文件期间
- 主服务器发送新写操作命令给从服务器
从服务器完成 RDB 的载入之后,会回复一个确认消息给主服务器,然后主服务器会将 replication buffer 缓冲区中记录的写操作命令发送给从服务器,从服务器执行完发送过来的命令后,主从服务器的数据就一致了
命令传播
主从服务器在完成第一次同步后双方之间会维护一个 TCP 连接,后续主服务器的操作可以通过这个连接将写操作命令传播给从服务器执行,使主从服务器数据库状态相同
这个连接是个长连接,目的是避免频繁的 TCP 连接和断开带来的性能开销
这个过程被称为 基于长连接的命令传播
主服务器压力分摊
如果从服务器数量非常多,并且都与主服务器进行全量同步的话,就会带来两个问题
- 由于使用 bgsave 生成 RDB 文件,数量过多,主服务器就会忙于使用 fork() 创建子进程,执行 fork() 时阻塞主线程,如果内存数据非常大,就有可能导致 Redis 长期阻塞,无法正常处理请求
- 传输 RDB 文件会占用主服务器网络带宽,对主服务器响应命令造成影响
解决方案
将主服务器生成 RDB 和传输 RDB 的压力分摊到从服务器上,由从服务器向其他的从服务器同步数据
replicaof <目标服务器的IP> 6379
如果目标服务器本身也是从服务器,则会在接受主服务器同步数据的同时,向自己旗下的从服务器同步数据,从而减轻主服务器负担
增量复制
如果主从服务器之间的网络连接断开无法进行命令传播,主从服务器数据不一致,在恢复后,从服务器会和主服务器重新进行一次全量复制(Redis 2.8之前),会带来额外的性能开销
在 Redis 2.8 之后,网络恢复后,主服务器会采用增量复制的方式继续同步,只同步网络断开期间接收到的写操作命令给从服务器
三个步骤
- 从服务器恢复网络后会发送 psync 命令给主服务器,此时 psync 命令中的 offset 不为 -1
- 主服务器接收到 psync 命令后使用 CONTINUE 命令通知从服务器接下来采用增量复制方式同步数据
- 主服务器将断线期间执行的写操作命令发送给从服务器执行
判断增量数据的方式
- repl_backlog_buffer: 一个环形缓冲区,用于主从服务器断连后查找差异数据,主服务器在进行命令传播时,不仅会将写命令发送给从服务器,还会写入这个缓冲区中
- replication offset:标记缓冲区的同步进度,主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 记录写到的位置,从服务器用 salve_repl_offset 记录读到的位置
主从服务器重连后,主服务器会根据从服务器 psync 命令中从服务器的偏移量决定同步方式
- 从服务器要读取的数据 还在 repl_backing_buffer 中 时,采用 增量复制
- 从服务器要读取的数据 不在 repl_backing_buffer 中 时,采用 全量同步
repl_backing_buffer 环形缓冲区默认大小为 1M,由于是环形的,因此缓冲区写满后如果继续写入,就会覆盖之前的数据,当主服务器的写入速度远超从服务器的读取速度时,缓冲区的数据很快就会被覆盖掉,导致网络恢复时,主服务器采用全量复制的方式向从服务器同步,造成远大于增量复制的性能损耗
为了避免网络恢复时主服务器频繁使用全量同步方式,可以调整 repl_backing_buffer 大小,减少从服务器读取数据被覆盖的概率,使主服务器采用增量同步
repl-backlog-size 1mb
面试题
Redis 主从节点采用长连接还是短连接:长连接
如何判断 Redis 某个节点是否正常工作
通过 ping-pong 心跳检测,半数以上的节点 ping 一个节点没有 pong 回应时,集群就会认定这个节点宕机,并断开连接
主从节点发送的心跳间隔不一样,并且作用有所区别- 主节点:默认每隔 10S 对从节点发送 ping 命令,判断从节点的存活性和连接状态,可通过 repl-ping-salve-period 控制频率
- 从节点:默认每隔一秒发送 replconf ack{offset} 命令,向主节点上报自身复制偏移量,目的是为了
- 实时监测主从节点网络状态
- 上报自身复制偏移量,检查复制数据是否丢失,如果从节点数据丢失,则从主节点的复制缓冲区中拉取丢失数据
主从复制架构中,过期 key 如何处理
主节点处理了一个 key 或通过淘汰算法淘汰了一个 key 之后,模拟一条 del 命令发送给从节点执行Redis 是同步复制还是异步复制
主节点每次收到写命令后,先写到内部缓冲区,再异步发送给从节点主从复制中两个 Buffer 的区别
replication buffer 和 repl backing buffer- 出现阶段
- repl backing buffer 在增量复制阶段出现,一个主节点只分配一个 repl backing Buffer
- replication buffer 在全量复制和增量复制阶段都会出现,主节点会给每个先连接的从节点分配一个 replication buffer
- 缓冲区满了之后
- repl backing buffer 是环形结构,满了之后会直接覆盖起始位置数据
- replication buffer 满了会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制
- 出现阶段
如何应对主从数据不一致
出现原因:主从节点间的命令复制是异步的,无法实现强一致性保证。具体表现在主从节点命令传播阶段,主节点接受新的写命令后会发送给从节点,但是不会等到从节点执行完毕后才返回结果给客户端,如果从节点没有成功执行主节点同步的命令,就会出现主从节点数据不一致
应对方案:第一种方案,尽量保证主从节点间网络连接状况良好
第二种方案,开发一个外部程序监控主从节点复制进度
具体做法:
使用 Redis 的 INFO replication 命令查看主节点接受写命令的进度信息 master_repl_offset 和 从节点复制写命令的进度信息 salve_repl_offset,然后计算主从节点差值,如果某个从节点的进度差值大于阈值,就不让客户端对它进行数据读取,就可以见到读取数据不一致的情况
主从切换减少数据丢失
主从切换过程中产生数据丢失的情况有两种- 异步复制同步丢失
主从节点间的数据复制是异步的,主节点还没来得及同步给从节点时就发生宕机,此时主节点内存中的数据会丢失
方案:
min-salves-max-lag 表示一旦所有从节点数据复制和同步的延迟超过阈值后,主节点会拒绝接受任何请求,这样就可以将主从节点的数据差控制在一定范围内
当客户端发现 master 不可写后,可以采用降级措施,将数据写入其他地方(本地缓存、磁盘、队列等),等 master 恢复正常后再重新写入 - 集群脑裂导致数据丢失
主从架构中部署方式一般是一主多从的,主节点负责写,从节点负责读
脑裂就是主节点网络故障期间,哨兵重新选取出新的主节点,旧主节点重新上线后会被降级为从节点,于是会清空自身缓存重新进行全量同步,导致之前写入旧主节点的数据丢失
方案
当主节点发现 下线的从节点数量太多 或者 网络延迟太多 时,会禁止写操作,直接把错误返回给客户端min-salves-to-write x
:表示从节点必须要有至少 x 个从节点连接,小于这个数主节点会禁止写数据min-salves-max-lag x
:表示主从数据复制和同步的延迟不能超过 x 秒,超过的话主节点会禁止写数据
这两个参数组合使用后表示: 主节点连接的从节点中至少需要有 N 个从节点,并且进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则主节点就不会再接收客户端的写请求
这样即使主节点是假性故障,在故障期间无法响应哨兵心跳,也无法和从节点进行同步,无法满足要求,就会被限制接收客户端写请求,等到新主节点上线,就只有新主节点能接收处理客户端请求,而原主节点会被降级为从节点,即使数据清空,也不会有新数据丢失
- 异步复制同步丢失
主从故障自动切换
主节点挂了之后从节点是无法自动升级为主节点的,需要人工操作,在此期间 Redis 无法对外提供写操作服务 此时就需要哨兵机制,在哨兵发现主节点故障时,自动完成故障发现和故障转移,并通知应用方,从而实现高可用性